package com.examples.gateway.helper.filter;

import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.StrUtil;
import com.examples.core.tool.constant.ExamplesConstants;
import com.examples.gateway.common.CheckState;
import com.examples.gateway.helper.Constants;
import com.examples.gateway.helper.config.GatewayHelperProperties;
import com.google.common.collect.Maps;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 获取请求接口的权限信息
 * @author Dai YaLu
 */
@Slf4j
public class PermissionDisableOrSkipFilter extends AbstractGlobalHelperFilter {

    private static final String CONTENT_DISPOSITION_TEMPLATE = "--{}\r\nContent-Disposition: form-data; name=\"{}\"\r\n\r\n";
    private static final List<HttpMessageReader<?>> MESSAGE_READERS = HandlerStrategies.withDefaults().messageReaders();
    private final AntPathMatcher matcher = new AntPathMatcher();

    @Autowired
    private GatewayHelperProperties helperProperties;

    @Override
    public Mono<Void> doFilter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        if (!helperProperties.getPermission().isEnabled()) {
            exchange.getAttributes().put(Constants.EXAMPLES_GATEWAY_CHECK_STATE, CheckState.SUCCESS_PERMISSION_DISABLED);
            return chain.filter(exchange);
        }
        GatewayHelperProperties.Permission permission = helperProperties.getPermission();
        // 白名单过滤
        if (permission.getSkipPaths().stream().anyMatch(t -> matcher.match(t, path))) {
            exchange.getAttributes().put(Constants.EXAMPLES_GATEWAY_CHECK_STATE, CheckState.SUCCESS_SKIP_PATH);
        }
        // 内部接口校验
        if (permission.getInternalPaths().stream().anyMatch(t -> matcher.match(t, path))) {
            return handleError(exchange, CheckState.PERMISSION_WITH_IN);
        }
        MediaType contentType = request.getHeaders().getContentType();
        if(path.endsWith(ExamplesConstants.OAUTH_TOKEN_URI) && MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)){
            return fileRequest(contentType,exchange,chain);
        }
        return chain.filter(exchange);
    }

    /**
     * 修改form参数
     * @param contentType
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> fileRequest(MediaType contentType,ServerWebExchange exchange, GatewayFilterChain chain){
        return DataBufferUtils.join(exchange.getRequest().getBody())
                .flatMap(dataBuffer -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);
                    //添加一些自定义参数，或者校验
                    String oldBody = addPara(contentType.toString(), new String(bytes));
                    byte[] bytes1 = oldBody.getBytes();
                    //这里截取数组是因为 form请求体数据末尾有--/r/n，这里需要截一段然后拼接自定义的数据。数据格式可以看文章末尾的链接
//                    byte[] bytes2 = byteMerger(Arrays.copyOf(bytes,bytes.length-4), bytes1);
                    Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes1);
                        DataBufferUtils.retain(buffer);
                        return Mono.just(buffer);
                    });
                    ServerHttpRequestDecorator mutatedRequest = newDecorator(exchange,bytes1.length,cachedFlux);
                    ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
                    return ServerRequest.create(mutatedExchange, MESSAGE_READERS)
                            .bodyToMono(byte[].class)
                            .then(chain.filter(mutatedExchange));
                });
    }

    @SneakyThrows
    public static String addPara(String contentType, String bodyString) {
        StringBuffer stringBuffer = new StringBuffer();
        //获取随机字符传信息
        String boundary = contentType.substring(contentType.lastIndexOf("boundary=") + 9);
        String boundaryEnd = StrUtil.format("--{}--\r\n", boundary);
        Map<String, Object> formMap = Maps.newHashMap();
        /**
         * 根据自己需求进行对bodyString信息修改，例如下面，根据boundary对传入的bodyString进行了分割
         *  String[] split = bodyString.split(boundary);
         *  然后将修改完后的信息封装到formMap中，需要注意的是，file文件需要以new FileResource(file, fileName)的形式当作value放到formMap中
         */
        String part = "^\r\nContent-Disposition: form-data; name=\"([^/?]+)\"\r\n\r\n([^/?]+)\r\n--?$";
        Pattern r = Pattern.compile(part);
        String[] split = bodyString.split(boundary);
        for(int x=1;x<split.length-1;x++){
            Matcher m = r.matcher(split[x]);
            if(m.find()){
                String name = m.group(1);
                String value = m.group(2);
                formMap.put(name,value);
            }
        }
        formMap.put("client_id",ExamplesConstants.OAUTH_CLIENT_ID);
        formMap.put("client_secret",ExamplesConstants.OAUTH_CLIENT_SECRET);
        Integer count =0;
        for (Map.Entry<String, Object> entry : formMap.entrySet()) {
            stringBuffer.append(appendPart(boundary, entry.getKey(), entry.getValue(),count));
            count++;
        }
        //拼接结束信息
        stringBuffer.append(boundaryEnd);
        return stringBuffer.toString();
    }

    /**
     * 添加Multipart表单的数据项
     * @param boundary      随机串信息
     * @param formFieldName 表单名
     * @param value         值，可以是普通值、资源（如文件等）
     */
    private static String appendPart(String boundary, String formFieldName, Object value,Integer count) throws IORuntimeException {
        StringBuffer stringBuffer = new StringBuffer();
        // 多资源
//        if(count!=0){
//            stringBuffer.append("--").append(boundary).append(StrUtil.CRLF);
//        }else{
//            stringBuffer.append(StrUtil.CRLF);
//        }
        // 普通数据
        stringBuffer.append(StrUtil.format(CONTENT_DISPOSITION_TEMPLATE,boundary, formFieldName)).append(value);
        stringBuffer.append(StrUtil.CRLF);
        return stringBuffer.toString();
    }

    public static byte[] byteMerger(byte[] bt1, byte[] bt2){
        byte[] bt3 = new byte[bt1.length+bt2.length];
        System.arraycopy(bt1, 0, bt3, 0, bt1.length);
        System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
        return bt3;
    }

    public ServerHttpRequestDecorator newDecorator(ServerWebExchange exchange,long dataLength,Flux<DataBuffer> body){
        return new ServerHttpRequestDecorator(
                exchange.getRequest()) {
            @Override
            public HttpHeaders getHeaders() {
                //数据长度变了以后 需要修改header里的数据，不然接收数据时会异常
                //我看别人说删除会自动补充数据长度，但我这个版本不太行
//				long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                httpHeaders.setContentLength(dataLength);
                return httpHeaders;
            }
            @Override
            public Flux<DataBuffer> getBody() {
                return body;
            }
        };
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
